package com.meyou.javascript.rewriter;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForInLoop;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.KeywordLiteral;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.mozilla.javascript.ast.VariableInitializer;

public class Rewriter {

	private RewriteEnvirons environs;
	
	private Stack<AstNode> rewriteScope = new Stack<AstNode>();
	
	public Rewriter(RewriteEnvirons environs) {
		this.environs = environs;
	}

	public ScriptNode rewriteTree(ScriptNode root) {
		
		if (environs.isAsync())
		{
			rewriteAsync(root);
		}
		else
		{
			rewriteSync(root);
		}
		return root;
	}
	
	protected void rewrite(Node node)
	{
		switch(node.getType())
		{
			case Token.FOR:
	            if (node instanceof ForInLoop) 
	            {
	                rewrite((ForInLoop)node);
	            } else {
	                rewrite((ForLoop)node);
	            }
	            break;
	            
			case Token.LT:
			case Token.INC:
			case Token.ADD:
				break;
				
			case Token.NOT:
				break;
				
			case Token.TRUE:
			case Token.FALSE:
				break;
				
			case Token.DELPROP:
				break;
				
			case Token.GETELEM:
				break;
				
			case Token.HOOK:
				break;
				
			case Token.NEW:
				break;
				
			case Token.EXPR_VOID:
				rewrite((ExpressionStatement) node);
				break;
				
			case Token.NULL:
				break;
	            
			case Token.STRING:
				rewrite((StringLiteral) node);
				break;
				
			case Token.IF:
				rewrite((IfStatement) node);
				break;
				
		    case Token.SCRIPT:
				rewrite((ScriptNode) node);
				break;
				
		    case Token.GETPROP:
		    	rewrite((PropertyGet) node);
		    	break;
		    	
		    case Token.RETURN:
		    	rewrite((ReturnStatement) node);
		    	break;
				
		    case Token.CALL:
		    	rewrite((FunctionCall) node);
		    	break;
				
		    case Token.FUNCTION:
		    	rewrite((FunctionNode) node);
		    	break;
		    	
		    case Token.ASSIGN:
		    	rewrite((Assignment) node);
		    	break;
		    	
		    case Token.NAME:
		    	rewrite((Name) node);
		    	break;
		    	
		    case Token.NUMBER:
		    	rewrite((NumberLiteral) node);
		    	break;
		    	
		    case Token.OBJECTLIT:
		    	rewrite((ObjectLiteral) node);
		    	break;
		    	
		    case Token.ARRAYLIT:
		    	rewrite((ArrayLiteral) node);
		    	break;
		
			default:
				
				if (node instanceof VariableDeclaration) 
				{
		            rewrite((VariableDeclaration)node);
		        }
				else if (node instanceof InfixExpression)
				{
					rewrite((InfixExpression) node);
				}
				else
				{
					throw new IllegalArgumentException("Can't rewrite: " + node);
				}
		}
		
	}
	
	protected void rewrite(FunctionNode node)
	{
		AstNode body = node.getBody();
		
		rewriteScope.push((AstNode) body);
		
		for (Node kid : body)
		{
			rewrite(kid);
		}
	}
	
	protected void rewriteAsync(ScriptNode node)
	{
		FunctionNode function = (FunctionNode) node.getFirstChild();
		
		boolean hasReturnValue = modifyReturnStatement(function);
		
		Block body = (Block) function.getBody();
		
		rewriteScope.push(body);
		
		for (Node kid : body)
		{
			rewrite(kid);
		}
		
		AstNode functionBody = createParenFunctionCall(  
														node, 
														null,
				                                        new String[]{
															environs.getType().toString(), 
															environs.getTypeId()
														},
														new String[]{
															RewriteToken.TYPES,
															RewriteToken.TYPE_ID
														}
													  );
		
		String genId = environs.genId();
		FunctionNode genFunctionNode = createFunction(genId, new String[0]);
		
		Block genBlock = new Block();
		genFunctionNode.setBody(genBlock);
		
		addChild(genFunctionNode, functionBody);
		
		removeChild(function, node);
		
		VariableDeclaration thatVar = createVarDeclaration(RewriteToken.THAT, createKeywordLiteral(Token.THIS));
		
		addChild(thatVar, genBlock);
		
		KeywordLiteral thisKeyword = createKeywordLiteral(Token.THIS);
		
		Name runName = createName(RewriteToken.RUN);
		
		ExpressionStatement runExpr = createAssignment(createPropertyGet(thisKeyword, runName), function);
		
		addChild(runExpr, genBlock);
		
		KeywordLiteral compKeyword = new KeywordLiteral();
		compKeyword.setType(Token.THIS);
		
		Name compName = createName(environs.getAsyncCallback());
		
		FunctionNode compFunctionNode = createFunction(null, hasReturnValue ? new String[] { environs.getAsyncCallbackParam()  } : new String[0]);
		
		Block compBlock = new Block();

		compFunctionNode.setBody(compBlock);
		
		ReturnStatement compFunctionRetStmt = createReturnStatement(hasReturnValue ? createName(environs.getAsyncCallbackParam()) : null);
		
		compBlock.addChild(compFunctionRetStmt);
		
		ExpressionStatement compExpr = createAssignment(createPropertyGet(compKeyword, compName), compFunctionNode);
		
		addChild(compExpr, genBlock);
		
		ExpressionStatement registerExpr = (ExpressionStatement) createMethodCall(RewriteToken.TYPES, RewriteToken.REGISTER_TYPE, 
																			new String[] { 
																				RewriteToken.TYPE_ID,
																				genId
																			});
		addChild(registerExpr, functionBody);
	}
	
	protected void rewriteSync(ScriptNode node)
	{
		FunctionNode function = (FunctionNode) node.getFirstChild();
		
		AstNode functionBody = createParenFunctionCall(  
				node, 
				null,
                new String[]{
					environs.getType().toString(), 
					environs.getTypeId()
				},
				new String[]{
					RewriteToken.TYPES,
					RewriteToken.TYPE_ID
				}
			  );
		
		Block genBlock = new Block();
		
		String genId = environs.genId();
		FunctionNode genFunctionNode = createFunction(genId, new String[0]);
		
		genFunctionNode.setBody(genBlock);
		
		addChild(genFunctionNode, functionBody);
		
		removeChild(function, node);
		
		VariableDeclaration thatVar = createVarDeclaration(RewriteToken.THAT, createKeywordLiteral(Token.THIS));
		
		addChild(thatVar, genBlock);
		
		KeywordLiteral thisKeyword = createKeywordLiteral(Token.THIS);
		
		Name runName = createName(RewriteToken.RUN);
		
		ExpressionStatement runExpr = createAssignment(createPropertyGet(thisKeyword, runName), function);
		
		addChild(runExpr, genBlock);
		
		ExpressionStatement registerExpr = (ExpressionStatement) createMethodCall(RewriteToken.TYPES, RewriteToken.REGISTER_TYPE, 
				new String[] { 
					RewriteToken.TYPE_ID,
					genId
				});
		addChild(registerExpr, functionBody);
		
	}
	
	protected void rewrite(FunctionCall node)
	{
		AstNode currentRewriteScope = rewriteScope.peek();
		
		AstNode parent = (AstNode) node.getParent();
		
		if (parent instanceof PropertyGet)
		{
			PropertyGet propGet = (PropertyGet) parent;
			
			String typeName = getName(propGet.getLeft());
			
			String callName = getName(node.getTarget());
			
			if (environs.needsRewrite(typeName, callName))
			{
				AstNode ancestorInScope = findAncestorInScope(propGet, currentRewriteScope);
				
				try
				{
					String asyncVarId = environs.genId();
					
					VariableDeclaration asyncVar = createVarDeclaration(asyncVarId, ancestorInScope);
					
					addChildBefore(asyncVar, ancestorInScope, currentRewriteScope);
					
				}
				finally
				{
					
				}
			}
		}
		
		if (parent instanceof VariableInitializer)
		{
			VariableInitializer var = (VariableInitializer) parent;
			
			if (var.getTarget() == node)
			{
			}
			else if (getInitializer(var) == node)
			{
				AstNode target = node.getTarget();
				
				if (target instanceof PropertyGet)
				{
					PropertyGet propGet = (PropertyGet) target;
					
					String typeName = getName(propGet.getLeft());
					
					String callName = getName(propGet.getRight());
					
					if (environs.needsRewrite(typeName, callName))
					{
						AstNode ancestorInScope = findAncestorInScope(propGet, currentRewriteScope);
						
						try
						{
							String asyncVarId = environs.genId();
							
							if (ancestorInScope instanceof VariableDeclaration)
							{
								VariableDeclaration asyncVar = createVarDeclaration(asyncVarId, node);
								
								addChildBefore(asyncVar, ancestorInScope, currentRewriteScope);
								
								Name compKeyword = createName(asyncVarId);
								compKeyword.setType(Token.THIS);
								
								FunctionNode compFunctionNode = createFunction(null, new String[] { environs.getAsyncCallbackParam()  });
								
								ExpressionStatement compExpr = createAssignment(createPropertyGet(createName(asyncVarId), createName(environs.getAsyncCallback())), compFunctionNode);
								
								addChildBefore(compExpr, ancestorInScope, currentRewriteScope);
								
								List<AstNode> nodesAfter = getNodesAfter(ancestorInScope, currentRewriteScope);
								
								Block compFunctionBody = new Block();
								
								rewriteScope.push(compFunctionBody);
								
								compFunctionNode.setBody(compFunctionBody);
								
								VariableDeclaration assignStmt = createVarDeclaration(getName(ancestorInScope), createName(environs.getAsyncCallbackParam()));
								
								addChild(assignStmt, compFunctionBody);
								
								for (AstNode nodeAfter : nodesAfter)
								{
									removeChild(nodeAfter, currentRewriteScope);
									addChild(nodeAfter, compFunctionBody);
								}
								
								removeChild(ancestorInScope, currentRewriteScope);
							}
							else
							{
								throw new IllegalStateException("unable to rewrite function call");
							}
						}
						finally
						{
						}
					}
				}
			}
		}
		
		if (parent instanceof ExpressionStatement)
		{
			AstNode target = node.getTarget();
			
			if (target instanceof PropertyGet)
			{
				PropertyGet propGet = (PropertyGet) target;
				
				String typeName = getName(propGet.getLeft());
				
				String callName = getName(propGet.getRight());
				
				if (environs.needsRewrite(typeName, callName))
				{
					AstNode ancestorInScope = findAncestorInScope(propGet, currentRewriteScope);
					
					try
					{
						String asyncVarId = environs.genId();
						
						if (ancestorInScope instanceof ExpressionStatement)
						{
							VariableDeclaration asyncVar = createVarDeclaration(asyncVarId, node);
							
							addChildBefore(asyncVar, ancestorInScope, currentRewriteScope);
							
							Name compKeyword = createName(asyncVarId);
							compKeyword.setType(Token.THIS);
							
							FunctionNode compFunctionNode = createFunction(null, new String[0] );
							
							ExpressionStatement compExpr = createAssignment(createPropertyGet(createName(asyncVarId), createName(environs.getAsyncCallback())), compFunctionNode);
							
							addChildBefore(compExpr, ancestorInScope, currentRewriteScope);
							
							List<AstNode> nodesAfter = getNodesAfter(ancestorInScope, currentRewriteScope);
							
							Block compFunctionBody = new Block();
							
							rewriteScope.push(compFunctionBody);
							
							compFunctionNode.setBody(compFunctionBody);
							
							for (AstNode nodeAfter : nodesAfter)
							{
								removeChild(nodeAfter, currentRewriteScope);
								addChild(nodeAfter, compFunctionBody);
							}
							
							removeChild(ancestorInScope, currentRewriteScope);
						}
						else
						{
							throw new IllegalStateException("unable to rewrite function call");
						}
					}
					finally
					{
					}
				}
			}
		}
	}
	
	protected void rewrite(IfStatement node)
	{
		AstNode currentRewriteScope = rewriteScope.peek();
		
		try
		{
			List<AstNode> generatedNodes = new ArrayList<AstNode>();
			
			AstNode condition = node.getCondition();
			
			AstNode genCondition = null;
			
			if (condition instanceof PropertyGet)
			{
				String condLeftId = environs.genId();
				
				PropertyGet propGet = (PropertyGet) condition;
				
				VariableDeclaration condLeftVar = createVarDeclaration(condLeftId, propGet);
				addChildBefore(condLeftVar, node, currentRewriteScope);
				
				generatedNodes.add(condLeftVar);
				
				genCondition = createName(condLeftId);
			} 
			else if (condition instanceof InfixExpression)
			{
				String condLeftId = environs.genId();
				String condRightId = environs.genId();
				
				InfixExpression infixExpr = (InfixExpression) condition;
				AstNode condLeft = infixExpr.getLeft();
				AstNode condRight = infixExpr.getRight();
				
				VariableDeclaration condLeftVar = createVarDeclaration(condLeftId, condLeft);
				addChildBefore(condLeftVar, node, currentRewriteScope);
				
				generatedNodes.add(condLeftVar);
				
				VariableDeclaration condRightVar = createVarDeclaration(condRightId, condRight);
				addChildBefore(condRightVar, node, currentRewriteScope);
				
				generatedNodes.add(condRightVar);
				
				genCondition = createInfixExpression(createName(condLeftId), createName(condRightId), infixExpr.getType());
			}
			else if (condition instanceof FunctionCall)
			{
				String condId = environs.genId();
				
				FunctionCall callExpr = (FunctionCall) condition;
				
				VariableDeclaration condVar = createVarDeclaration(condId, callExpr);
				addChildBefore(condVar, node, currentRewriteScope);
				
				generatedNodes.add(condVar);
				
				genCondition = createName(condId);
			}
			else if (condition instanceof Name)
			{
				String condId = environs.genId();
				
				Name name = (Name) condition;
				
				VariableDeclaration condVar = createVarDeclaration(condId, name);
				addChildBefore(condVar, node, currentRewriteScope);
				
				generatedNodes.add(condVar);
				
				genCondition = createName(condId);
			}
			else if (condition instanceof UnaryExpression)
			{
				String condId = environs.genId();
				
				UnaryExpression unaryExpr = (UnaryExpression) condition;
				
				VariableDeclaration condVar = createVarDeclaration(condId, unaryExpr);
				addChildBefore(condVar, node, currentRewriteScope);
				
				generatedNodes.add(condVar);
				
				genCondition = createName(condId);
			}
			else
			{
				throw new IllegalStateException("could not rewrite if statement");
			}

			node.setCondition(genCondition);
			
			AstNode thenPart = (AstNode) node.getThenPart();
			
			if (thenPart != null)
			{
				try
				{
					rewriteScope.push(thenPart);
					
					for (Node thenNode : thenPart)
					{
						rewrite(thenNode);
					}
				}
				finally
				{
					rewriteScope.pop();
				}
			}
			
			AstNode elsePart = (AstNode) node.getElsePart();
			if (elsePart != null)
			{
				try
				{
					rewriteScope.push(elsePart);
					
					for (Node elseNode : elsePart)
					{
						rewrite(elseNode);
					}
				}
				finally
				{
					rewriteScope.pop();
				}
			}
			
			for (AstNode generatedNode : generatedNodes)
			{
				rewrite(generatedNode);
			}
			
		}
		finally
		{
		}
		
	}
	
	protected void rewrite(ForLoop node) 
	{
		List<AstNode> generatedNodes = new ArrayList<AstNode>();
		
		AstNode currentRewriteScope = rewriteScope.peek();
		
		try
		{
			AstNode initializer = getInitializer(node);
			String initializerId = getName(initializer);
			
			addChildBefore(initializer, node, currentRewriteScope);
			generatedNodes.add(initializer);
			
			String funcName = environs.genId();
			
			List<AstNode> elseNodes = getNodesAfter(node, currentRewriteScope);
			
			for (Node elseNode : elseNodes)
			{
				removeChild((AstNode) elseNode, currentRewriteScope);
			}
			
			Block body = (Block) createParenFunctionCall(currentRewriteScope, funcName, new String[] { initializerId },  new String[] { initializerId } );
			
			AstNode nodeBody = node.getBody();
			
			String condId = environs.genId();
			
			IfStatement ifStmt = null;
			
			AstNode condition = node.getCondition();
			
			if (condition instanceof InfixExpression)
			{
				InfixExpression infixExpr = (InfixExpression) condition;
				AstNode condRight = infixExpr.getRight();
				
				VariableDeclaration condVar = createVarDeclaration(condId, condRight);
				
				addChildBefore(condVar, node, currentRewriteScope);
				
				generatedNodes.add(condVar);
				
				ifStmt = createIfStatement(createInfixExpression(createName(initializerId), createName(condId), Token.LT));
				
				addChild(ifStmt, body);
			}
			else if (condition instanceof FunctionCall)
			{
				FunctionCall callExpr = (FunctionCall) condition;
				
				VariableDeclaration condVar = createVarDeclaration(condId, callExpr);
				
				addChild(condVar, body);
				
				ifStmt = createIfStatement(createName(condId));
				
				addChild(ifStmt, body);
			}
			else
			{
				throw new IllegalStateException("could not rewrite for loop");
			}
			
			List<AstNode> thenNodes = new ArrayList<AstNode>();
			
			List<AstNode> origKids = new ArrayList<AstNode>(); 
			
			for (Node kid : nodeBody)
			{
				thenNodes.add((AstNode) kid);
				origKids.add((AstNode) kid);
				removeChild((AstNode) kid, nodeBody);
			}
			
			AstNode increment = node.getIncrement();
			FunctionCall recursiveCall = createFunctionCall(funcName, new Object[] { increment });
			
			thenNodes.add(recursiveCall);
			
			Block thenPart = new Block();
			ifStmt.setThenPart(thenPart);
			for (Node thenNode : thenNodes)
			{
				addChild((AstNode) thenNode, thenPart);
			}
			
			Block elsePart = new Block();
			for (Node elseNode : elseNodes)
			{
				addChild((AstNode) elseNode, elsePart);
			}
			ifStmt.setElsePart(elsePart);
			
			removeChild(node, currentRewriteScope);
			
			for (AstNode generatedNode : generatedNodes)
			{
				rewrite(generatedNode);
			}
			
			rewriteScope.push(thenPart);
			
			for (Node orgKid : origKids)
			{
				rewrite(orgKid);
			}
			
		}
		finally
		{
			rewriteScope.pop();
		}
	}
	
	protected void rewrite(ForInLoop node) 
	{
		List<AstNode> generatedNodes = new ArrayList<AstNode>();
		
		AstNode currentRewriteScope = rewriteScope.peek();
		
		try
		{
			AstNode iterator = node.getIterator();
			AstNode iterated = node.getIteratedObject();
			String loopIterator = getName(iterator);
			
			String loopIterated = null;
			
			String arrId = environs.genId();
			VariableDeclaration arrDecl = createVarDeclaration(arrId, new ArrayLiteral());
			
			addChildBefore(arrDecl, node, currentRewriteScope);
			
			if (iterated instanceof Name)
			{
				loopIterated = getName(iterated);
			}
			else if (iterated instanceof FunctionCall)
			{
				FunctionCall iteratedExpr = (FunctionCall) iterated;
				
				String iteratedVarId = environs.genId();
				VariableDeclaration iteratedVar = createVarDeclaration(iteratedVarId, iteratedExpr);
				
				addChildBefore(iteratedVar, node, currentRewriteScope);
				
				generatedNodes.add(iteratedVar);
				
				loopIterated = iteratedVarId;
			} 
			else
			{
				throw new IllegalStateException("cannot rewrite for in loop");
			}
			
			String arrIterator = environs.genId();
			ForInLoop arrLoop = createForInLoop(arrIterator, loopIterated, false);
			
			AstNode arrPush = createMethodCall(arrId, "push", new String[] { arrIterator });
			Block arrBody = new Block();
			
			addChild(arrPush, arrBody);
			arrLoop.setBody(arrBody);
			addChildBefore(arrLoop, node, currentRewriteScope);
			
			String lenId = environs.genId();
			VariableDeclaration lenDecl = createVarDeclaration(lenId, createPropertyGet(createName(arrId), createName("length")));
			addChildBefore(lenDecl, node, currentRewriteScope);
			
			String funcName = environs.genId();
			String param = environs.genId(); 
			
			List<AstNode> elseNodes = getNodesAfter(node, currentRewriteScope);
			
			for (Node elseNode : elseNodes)
			{
				removeChild((AstNode) elseNode, currentRewriteScope);
			}
			
			Block body = (Block) createParenFunctionCall(currentRewriteScope, funcName, new String[] { "0" },  new String[] { param } );
			
			IfStatement ifStmt = createIfStatement(createInfixExpression(createName(param), createName(lenId), Token.LT));
			
			addChild(ifStmt, body);
			
			AstNode nodeBody = node.getBody();
			
			List<AstNode> thenNodes = new ArrayList<AstNode>();
			
			if (node.isForEach())
			{
				String elemGetName = environs.genId();
				ElementGet elemGet = createElementGet(createName(arrId), createName(param));
				VariableDeclaration elemGetVar = createVarDeclaration(elemGetName, elemGet);
				thenNodes.add(elemGetVar);
				
				if (iterator instanceof VariableDeclaration)
				{
					VariableDeclaration iteratorVar = createVarDeclaration(loopIterator, createElementGet(createName(loopIterated), createName(elemGetName)));
					thenNodes.add(iteratorVar);
				}
				else if (iterator instanceof Name)
				{
					ExpressionStatement iteratorStmt = createAssignment(createName(loopIterator), createElementGet(createName(loopIterated), createName(elemGetName)));
					thenNodes.add(iteratorStmt);
				}
				else
				{
					throw new IllegalStateException("unexpected iterator");
				}
			}
			else
			{
				if (iterator instanceof VariableDeclaration)
				{
					VariableDeclaration iteratorVar = createVarDeclaration(loopIterator, createElementGet(createName(arrId), createName(param)));
					thenNodes.add(iteratorVar);
				}
				else if (iterator instanceof Name)
				{
					ExpressionStatement iteratorStmt = createAssignment(createName(loopIterator), createElementGet(createName(arrId), createName(param)));
					thenNodes.add(iteratorStmt);
				}
				else
				{
					throw new IllegalStateException("unexpected iterator");
				}
			}
			
			List<AstNode> origKids = new ArrayList<AstNode>(); 
			
			for (Node kid : nodeBody)
			{
				thenNodes.add((AstNode) kid);
				origKids.add((AstNode) kid);
				removeChild((AstNode) kid, nodeBody);
			}
			
			InfixExpression nextExpr = createInfixExpression(createName(param), createName("1"), Token.ADD);
			FunctionCall recursiveCall = createFunctionCall(funcName, new Object[] { nextExpr });
			
			thenNodes.add(recursiveCall);
			
			Block thenPart = new Block();
			ifStmt.setThenPart(thenPart);
			for (Node thenNode : thenNodes)
			{
				addChild((AstNode) thenNode, thenPart);
			}
			
			Block elsePart = new Block();
			for (Node elseNode : elseNodes)
			{
				addChild((AstNode) elseNode, elsePart);
			}
			ifStmt.setElsePart(elsePart);
			
			removeChild(node, currentRewriteScope);
			
			for (AstNode generatedNode : generatedNodes)
			{
				rewrite(generatedNode);
			}
			
			rewriteScope.push(thenPart);
			
			for (Node orgKid : origKids)
			{
				rewrite(orgKid);
			}
			
			
			
		}
		finally
		{
			rewriteScope.pop();
		}
	}
	
	protected void rewrite(Assignment node)
	{
		rewrite(node.getLeft());
		rewrite(node.getRight());
	}
	
	protected void rewrite(VariableDeclaration node) 
	{
		List<VariableInitializer> vars = node.getVariables();
		
		for (VariableInitializer var : vars)
		{
			rewrite(var.getTarget());
			
			if (var.getInitializer() != null)
			{
				rewrite(var.getInitializer());
			}
		}
    }
	
	protected void rewrite(ExpressionStatement node)
	{
		Node expr = node.getExpression();
		rewrite(expr);
	}
	
	protected void rewrite(InfixExpression node)
	{
	}
	
	protected void rewrite(ReturnStatement node)
	{
	}
	
	protected void rewrite(NumberLiteral node)
	{
	}
	
	protected void rewrite(ObjectLiteral node)
	{
	}
	
	protected void rewrite(ArrayLiteral node)
	{
	}
	
	protected void rewrite(Name node)
	{
	}
	
	protected void rewrite(StringLiteral node)
	{
	}
	
	protected void rewrite(PropertyGet node)
	{
	}
	
	protected boolean modifyReturnStatement(FunctionNode funcNode)
	{
		boolean hasReturnValue = false;
		
		Block funcBody = (Block) funcNode.getBody();
		
		ReturnStatement node = null;
		
		for (Node kid : funcBody)
		{
			if (kid instanceof ReturnStatement)
			{
				node  = (ReturnStatement) kid;
			}
		}
		
		if (node == null)
		{
			node = new ReturnStatement();
			
			addChild(node, funcBody);
		}
		
		ExpressionStatement stmt = new ExpressionStatement();
		FunctionCall onComplete = new FunctionCall();
		stmt.setExpression(onComplete);
		
		List<AstNode> args = new ArrayList<AstNode>();
		AstNode returnValue = node.getReturnValue();
		if (returnValue != null)
		{
			args.add(returnValue);
			hasReturnValue = true;
		}
		
		onComplete.setArguments(args);
		
		PropertyGet thisOnComplete = new PropertyGet();
		onComplete.setTarget(thisOnComplete);
		
		Name thisName = new Name();
		thisName.setIdentifier(RewriteToken.THAT);
		thisOnComplete.setLeft(thisName);
		
		Name onCompleteName = new Name();
		onCompleteName.setIdentifier(environs.getAsyncCallback());
		thisOnComplete.setRight(onCompleteName);
		
		AstNode parent = node.getParent();
		
		removeChild(node, parent);
		
		addChild(stmt, funcBody);
		
		return hasReturnValue;
		
	}
	
	protected AstNode findReturnValue(FunctionNode node)
	{
		Block body = (Block) node.getBody();
		
		for (Node kid : body)
		{
			if (kid instanceof ReturnStatement)
			{
				ReturnStatement retStmt = (ReturnStatement) kid;
				
				return retStmt.getReturnValue();
			}
		}
		
		return null;
	}
	
	protected void addChildBefore(AstNode newChildNode, AstNode childNode, AstNode node) {
		
		node.addChildBefore(newChildNode, childNode);
		newChildNode.setParent(node);
	}
	
	protected void addChild(AstNode childNode, AstNode node)
	{
		node.addChild(childNode);
		childNode.setParent(node);
	}
	
	protected void removeChild(AstNode childNode, AstNode node)
	{
		node.removeChild(childNode);
		childNode.setParent(null);
	}
	
	protected List<AstNode> getNodesAfter(AstNode node, AstNode enclosingNode)
	{
		if (enclosingNode == null)
			throw new IllegalArgumentException("Cannot get nodes after");
		
		AstNode ancestorInScope = findAncestorInScope(node, enclosingNode);
		if (ancestorInScope == null)
			throw new IllegalArgumentException("Cannot find ancestor");
		
		List<AstNode> nodesAfter = new ArrayList<AstNode>();
		
		AstNode parent = ancestorInScope.getParent();
		
		AstNode child = (AstNode) parent.getLastChild();
		
		while (child != null && child != ancestorInScope)
		{
			nodesAfter.add(0, child);
			child = (AstNode) parent.getChildBefore(child);
		}
		
		return nodesAfter;	
	}
	
	protected AstNode findAncestorInScope(AstNode node, AstNode enclosingNode)
	{
		AstNode ancs = node;
		
		while (ancs != null && ancs != enclosingNode && ancs.getParent() != enclosingNode)
		{
			ancs = ancs.getParent(); 
		}
		
		return ancs;
		
	}
	
	protected AstNode findAncestorInFunctionScope(AstNode node, FunctionNode enclosingNode)
	{
		AstNode currentParent = enclosingNode.getBody();
		AstNode ancs = node;
		
		while (ancs != null && ancs != enclosingNode && ancs.getParent() != currentParent)
		{
			ancs = ancs.getParent(); 
		}
		
		if (ancs == enclosingNode)
		{
			throw new IllegalStateException("no parent found in function body");
		}
		
		return ancs;
	}
	
	protected AstNode getInitializer(AstNode node)
	{
		if (node instanceof VariableInitializer)
		{
			return ((VariableInitializer) node).getInitializer();
		}
		
		if (node instanceof VariableDeclaration)
		{
			List<VariableInitializer> vars = ((VariableDeclaration) node).getVariables();
			
			if (vars == null) return null;
			
			if (vars.isEmpty()) return null;
			
			VariableInitializer var = vars.get(0);
			
			return getInitializer(var);
		}
		
		if (node instanceof ForLoop)
		{
			return ((ForLoop) node).getInitializer();
		}
		
		return null;
	}
	
	protected String getName(AstNode node)
	{
		if (node instanceof Name)
		{
			Name name = (Name) node;
			return name.getIdentifier();
		}
		
		if (node instanceof VariableDeclaration)
		{
			VariableDeclaration decl  = (VariableDeclaration) node;
			VariableInitializer var =  decl.getVariables().get(0);
			
			return ((Name) var.getTarget()).getIdentifier();
		}
		
		if (node instanceof Assignment)
		{
			Assignment assign = (Assignment) node;
			Name name = (Name) assign.getLeft();
			return name.getIdentifier();
		}
		
		if (node instanceof FunctionCall)
		{
			FunctionCall func = (FunctionCall) node;
			AstNode target = func.getTarget();
			
			return getName(target);
		}
		
		if (node instanceof ElementGet)
		{
			ElementGet get = (ElementGet) node;
			
			return getName(get.getElement());
		}
		
		throw new IllegalStateException("Cannot get name");
	}
	
	protected RewriteTypeEnum getRewriteType(FunctionCall node)
	{
		PropertyGet propertyGet = (PropertyGet) node.getTarget();
		Name name = (Name) propertyGet.getLeft();
		
		String identifier = name.getIdentifier();
		
		return RewriteTypeEnum.valueOf(identifier);
	}
	
	protected RewriteMethodEnum getRewriteMethod(FunctionCall node)
	{
		PropertyGet propertyGet = (PropertyGet) node.getTarget();
		Name name = (Name) propertyGet.getRight();
		
		String identifier = name.getIdentifier();
		
		return RewriteMethodEnum.valueOf(identifier);
	}
	
	protected boolean needsRewrite(FunctionCall node)
	{
		PropertyGet target = (PropertyGet) node.getTarget();
		Name typeNode = (Name) target.getLeft();
		Name callNode = (Name) target.getRight();
		
		return environs.needsRewrite(typeNode.getIdentifier(), callNode.getIdentifier());
	}
	
	protected Rewriter setNameAsTarget(AstNode node, String id)
	{
		Name name = createName(id);
		
		if (node instanceof VariableInitializer)
		{
			((VariableInitializer) node).setTarget(name);
		}
		
		return this;
	}
	
	protected Rewriter setNameLeft(AstNode node, String id)
	{
		Name name = createName(id);
		
		if (node instanceof Assignment)
		{
			((Assignment) node).setLeft(name);
		}
		
		if (node instanceof PropertyGet)
		{
			((PropertyGet) node).setLeft(name);
		}
		
		return this;
	}
	
	protected Rewriter setNameRight(AstNode node, String id)
	{
		Name name = createName(id);
		
		if (node instanceof Assignment)
		{
			((Assignment) node).setRight(name);
		}
		
		if (node instanceof PropertyGet)
		{
			((PropertyGet) node).setRight(name);
		}
		
		return this;
	}
	
	protected Name createName(String id)
	{
		Name name = new Name();
		name.setIdentifier(id);
		
		return name;
	}
	
	protected KeywordLiteral createKeywordLiteral(int type)
	{
		KeywordLiteral keyword = new KeywordLiteral();
		keyword.setType(type);
		
		return keyword;
	}
	
	protected ExpressionStatement createAssignment(Node assignLeft, Node assignRight)
	{
		ExpressionStatement expr = new ExpressionStatement();
		
		Assignment assign = new Assignment();
		expr.setExpression(assign);
		
		assign.setLeft((AstNode) assignLeft);
		assign.setRight((AstNode) assignRight);
		
		assign.setType(Token.ASSIGN);
		
		return expr;
		
	}
	
	protected PropertyGet createPropertyGet(AstNode left, AstNode right)
	{
		PropertyGet get = new PropertyGet();
		
		get.setLeft(left);
		get.setRight(right);
		
		return get;
	}
	
	protected ElementGet createElementGet(AstNode target, AstNode elem)
	{
		ElementGet elemGet = new ElementGet();
		
		elemGet.setElement(elem);
		elemGet.setTarget(target);
		
		return elemGet;
		
	}
	
	protected VariableDeclaration createVarDeclaration(String name, AstNode initializer)
	{
		VariableDeclaration decl = new VariableDeclaration();
		
		List<VariableInitializer> vars = new ArrayList<VariableInitializer>();
		VariableInitializer var = new VariableInitializer();
		var.setTarget(createName(name));
		var.setInitializer(initializer);
		
		initializer.setParent(var);
		
		vars.add(var);
		
		decl.setVariables(vars);
		
		return decl;
	}
	
	protected FunctionCall createFunctionCall(String func, Object[] args)
	{
		FunctionCall call = new FunctionCall();
		Name funcName = createName(func);
		call.setTarget(funcName);
		
		List<AstNode> argsNames = new ArrayList<AstNode>();
		
		for (int i = 0; i < args.length; i++)
		{
			if (args[i] instanceof String)
			{
				argsNames.add(createName((String) args[i]));
			}
			else
			{
				argsNames.add((AstNode) args[i]);
			}
		}

		call.setArguments(argsNames);
		
		return call;
	}
	
	protected AstNode createMethodCall(String type, String func, Object[] args)
	{
		ExpressionStatement expr = new ExpressionStatement();
		
		PropertyGet propGet = new PropertyGet();
		expr.setExpression(propGet);
		
		Name typesName = createName(type);
		propGet.setLeft(typesName);
		
		FunctionCall call = createFunctionCall(func, args);
		propGet.setRight(call);
		
		return expr;
	}
	
	protected ForInLoop createForInLoop(String iterator, String iterated, boolean isForEach)
	{
		ForInLoop loop = new ForInLoop();
		loop.setIsForEach(isForEach);
		
		VariableDeclaration iteratorVarDecl = new VariableDeclaration();
		List<VariableInitializer> iteratorVars = new ArrayList<VariableInitializer>();
		
		VariableInitializer iteratorVar = new VariableInitializer();
		Name iteratorName = createName(iterator);
		iteratorVar.setTarget(iteratorName);
		
		iteratorVars.add(iteratorVar);
		iteratorVarDecl.setVariables(iteratorVars);
		
		Name iteratedName = createName(iterated);
		loop.setIteratedObject(iteratedName);
		loop.setIterator(iteratorVarDecl);
		
		return loop;
	}
	
	protected FunctionNode createFunction(String funcName, String[] params)
	{
		FunctionNode functionNode = new FunctionNode();
		if (funcName != null)
		{
			functionNode.setFunctionName(createName(funcName));
		}
		
		List<AstNode> paramNames = new ArrayList<AstNode>();
		
		for (int i = 0; i < params.length; i++)
		{
			paramNames.add(createName(params[i]));
		}
		
		functionNode.setParams(paramNames);
		
		return functionNode;
	}
	
	protected ReturnStatement createReturnStatement(AstNode returnValue)
	{
		ReturnStatement returnNode = new ReturnStatement();
		
		if (returnValue != null)
			returnNode.setReturnValue(returnValue);
		
		return returnNode;
	}
	
	protected InfixExpression createInfixExpression(AstNode left, AstNode right, int token)
	{
		InfixExpression expr = new InfixExpression();
		expr.setLeft(left);
		expr.setRight(right);
		expr.setType(token);
		
		return expr;
	}
	
	protected IfStatement createIfStatement(AstNode condition)
	{
		IfStatement ifStmt = new IfStatement();
		ifStmt.setCondition(condition);
		return ifStmt;
	}
	
	protected AstNode createParenFunctionCall(AstNode parent, String funcName, String[] args, String[] params)
	{
		ExpressionStatement stmt = new ExpressionStatement();
		addChild(stmt, parent);
		
		FunctionCall call = new FunctionCall();
		stmt.setExpression(call);
		
		List<AstNode> argNames = new ArrayList<AstNode>();
		
		for (int i = 0; i < args.length; i++)
		{
			argNames.add(createName(args[i]));
		}
		
		call.setArguments(argNames);
		
		ParenthesizedExpression parenExpr = new ParenthesizedExpression();
		call.setTarget(parenExpr);
		
		FunctionNode functionNode = createFunction(funcName, params);
		parenExpr.setExpression(functionNode);
		
		Block functionBody = new Block();
		functionNode.setBody(functionBody);
		
		return functionBody;
	}
	
}
